package icasue.webdashboard.apptree.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import icasue.base.register.ICasuePluginsRegister;
import icasue.base.touch.ApplicationTouch;
import icasue.base.utils.wareable.CLog;
import icasue.reflect.handles.HandleSupplier;
import icasue.reflect.handles.arrays.AryOF;
import icasue.reflect.handles.fields.FieldOF;
import icasue.reflect.handles.object.ObjectOF;
import icasue.reflect.handles.util.OFUtil;
import icasue.tools.compile.utils.DCUtil;
import icasue.webdashboard.api.DashboardApiWare;
import icasue.webdashboard.apptree.buffer.FileMappingBuffer;
import icasue.webdashboard.apptree.config.properties.AppTreeProperties;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

import javax.validation.constraints.NotNull;
import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/12/1 下午1:16
 * @UpdateDate:
 * @Description:
 */
public class PluginDashboardBeanRegisterGroup {

    public static class AppTreeRegister extends ICasuePluginsRegister<AppTreeProperties> {
        private static final String HOST_PORT_FORMAT = "%s:%s";
        @Override
        public String environmentPropertiesPrefix() {
            return "dashboard.tree";
        }

        @Override
        public Class<AppTreeProperties> environmentPropertiesType() {
            return AppTreeProperties.class;
        }

        @Override
        protected @NotNull Boolean checkRegister() {
            return super.configProperties.enable;
        }

        @Override
        protected void previousRegister() {
            new Thread(() -> {
                rebuildURLTemplate();
                Boolean readyLocal = readLocal();
                if(!readyLocal){ CLog.info(" \t ###### icasue-plugin-web-dashboard :: Local mode init error, suggest debug to check environment pls."); }
                Boolean readyCluster = readCluster();
                if(!readyCluster){ CLog.info(" \t ###### icasue-plugin-web-dashboard :: Cluster mode not init."); }
            }).start();
        }

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            String[] excludeRegex = configProperties.excludeRegex.stream().toArray(String[]::new);
            final String[] excludeCopy = (String[]) AryOF.copy_.apply(excludeRegex,excludeRegex.length);
            new Thread(() -> fileListDigest(excludeCopy)).start();
        }

        private void fileListDigest(String[] excludePattern){
            JSONObject filePathTree = new JSONObject();
            URL resource = AppTreeRegister.class.getClassLoader().getResource("");
            String rootPath = resource.getFile();
            String rootPathVisible = environment.getProperty("spring.application.name",rootPath);

            //Current application scene as Deploying mode. using
            int currentDeployJarMode = rootPath.indexOf(".jar");
            if(currentDeployJarMode > 0){
                rootPath = rootPath.replace("!", Strings.EMPTY).replaceFirst("\\.jar", "-depress");
                if(rootPath.startsWith("file:"))
                    rootPath = rootPath.substring(5);
                //Read DynamicCompile environment's depress ready.
                while (ObjectOF.equals_.test(Boolean.FALSE, FieldOF.get_ofTp_fName_instance_.apply(DCUtil.class,"dynamicCompileEnvReady",null))){ };
            }

            File rootFile = new File(rootPath);
            String os = System.getProperty("os.name");
            if(rootFile.exists()) {
                String fileJson = readDirToJson(rootFile, os.contains("Win") ? rootPath.length() - 1 : rootPath.length(),excludePattern);
                filePathTree = JSON.parseObject(fileJson, JSONObject.class);
                filePathTree.put("label", rootPath);
            }
            //Hidden root path.
            if(filePathTree != null) filePathTree.replace("label",rootPathVisible);
            //装载.
            FieldOF.set_ofTp_fName_instance_val_.accept(FileMappingBuffer.class, "rootMapping", null, Arrays.asList(filePathTree));
        }

        /**
         * File path recursion to organize construct WEB DATA.
         * @param file
         * @param rootLen
         * @return
         */
        private String readDirToJson(File file,Integer rootLen,String[] excludePattern){
            Predicate<String> filterFile = (Predicate<String>)FieldOF.get_ofTp_fName_instance_.apply(FileMappingBuffer.class, "filterFile", null);
            File [] listFiles = file.listFiles((dir,filename) -> recursionDirFilter(dir,excludePattern));
            String dirName=file.getName();
            String fileJson="{\"label\":\""+dirName+"\",\"children\":[";
            int size=listFiles.length;
            for (int i=0;i<size; i++) {
                File file2=listFiles[i];
                if(file2.isDirectory()){
                    fileJson=fileJson+readDirToJson(file2,rootLen,excludePattern);
                    if(i<size){
                        fileJson=fileJson+",";
                    }
                }else {
                    String path=file2.getPath().substring(rootLen);
                    String fileStr="";
                    String fileName=file2.getName();
                    if(i<size){
                        if(fileName.endsWith(".class") && filterFile.test(fileName)){
                            String cls = path.replace(File.separator,".").replaceAll(".class$", Strings.EMPTY);
                            fileStr="{cls:\""+cls+"\",label:\""+fileName+"\"}";
                            fileStr=fileStr+",";
                            fileJson=fileJson+fileStr;
                        }
                    }
                }
            }
            fileJson=fileJson+"]}";
            return fileJson;
        }

        /**
         * 递归当前目录下包不包含class文件
         * @param dir
         * @return
         */
        private static Boolean recursionDirFilter(File dir,String[] excludePattern){
            if (excludePattern != null && excludePattern.length > 0) {
                for (String excludeUnit : excludePattern) {
                    String path = dir.getPath().toString();
                    if(path.matches(excludeUnit)) return false;
                }
            }
            return true;
        };


        /**
         * Rebuild UrlTemplate.
         * Add servlet-path for application root.
         * @return
         */
        private boolean rebuildURLTemplate(){
            String contextPath = environment.getProperty("server.servlet.context-path", Strings.EMPTY);
            String uriTemplate = (String)FieldOF.get_ofTp_fName_instance_.apply(DashboardApiWare.class, "URI_TEMPLATE", null);
            String uriTemplateWithContext = String.format(uriTemplate, "%s", contextPath);
            FieldOF.set_ofTp_fName_val_.accept(DashboardApiWare.class, "URI_TEMPLATE", uriTemplateWithContext);
            return Boolean.TRUE;
        }


        /**
         * Ready Cluster environment.
         * @return
         */
        private boolean readCluster(){
            try {
                String appName = environment.getProperty("spring.application.name", "");
                if(Strings.EMPTY.equals(appName)) {
                    CLog.info("\t ######## icasue-plugin-web-dashboard :: read property of ${spring.application.name} fail, strategy of cluster mode will un used.");
                    return false;
                }else {
                    //Check discoveryClient and init ClusterMode.
                    Class<?> discoveryClientCls = Class.forName("org.springframework.cloud.client.discovery.DiscoveryClient");
                    Class<?> serviceInstance = Class.forName("org.springframework.cloud.client.ServiceInstance");
                    while (ApplicationTouch.applicationContext == null){ /*Wait environment ready.*/ }
                    Object discoveryClient = ApplicationTouch.applicationContext.getBean(discoveryClientCls);
                    MethodHandle getInstances = HandleSupplier.methodLookup.findVirtual(discoveryClientCls, "getInstances", MethodType.methodType(List.class, String.class));
                    MethodHandle getHost = HandleSupplier.methodLookup.findVirtual(serviceInstance, "getHost", MethodType.methodType(String.class));
                    MethodHandle getPort = HandleSupplier.methodLookup.findVirtual(serviceInstance, "getPort", MethodType.methodType(int.class));
                    Function<String, Set<String>> clusterModeServerIdentityTree = (serviceId) -> {
                        try {
                            return (Set<String>) OFUtil.cast(getInstances.bindTo(discoveryClient).invoke(serviceId),List.class)
                                    .stream().map((inst) -> {
                                        try { return String.format(HOST_PORT_FORMAT, getHost.bindTo(inst).invoke(), getPort.bindTo(inst).invoke()); } catch (Throwable e) {
                                            CLog.error("\t ####### icasue-plugin-web-dashboard :: Get balancer's host port occur an error, this balancer node will be ignore.");
                                            return null;
                                        }
                                    }).filter(item -> item != null).collect(Collectors.toSet());
                        }catch (Throwable e){ /**/ }
                        return Collections.EMPTY_SET;
                    };
                    FieldOF.set_ofTp_fName_instance_val_.accept(DashboardApiWare.class,"appName",null,appName);
                    FieldOF.set_ofTp_fName_instance_val_.accept(DashboardApiWare.class,"discoveryClient",null,discoveryClient);
                    FieldOF.set_ofTp_fName_instance_val_.accept(DashboardApiWare.class,"clusterModeServerIdentityTree",null,clusterModeServerIdentityTree);
                }
                return true;
            }catch (Throwable e){
                CLog.error("\t ####### icasue-plugin-web-dashboard :: Cluster mode prepare fail, cluster mode will unUsed, or check class : org.springframework.cloud.client.discovery.DiscoveryClient's exist or not pls.");
                return false;
            }

        }

        /**
         * Ready Local environment.
         * @return
         */
        private Boolean readLocal(){
            try {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
                String port = environment.getProperty("server.port", "8080");
                String uriTemplate = (String)FieldOF.get_ofTp_fName_instance_.apply(DashboardApiWare.class, "URI_TEMPLATE", null);
                String localNode = String.join(":", "localhost", port);
                FieldOF.set_ofTp_fName_instance_val_.accept(DashboardApiWare.class,"localNode",null,localNode);
                FieldOF.set_ofTp_fName_instance_val_.accept(DashboardApiWare.class,"localAppURI",null,new URI(String.format(uriTemplate, localNode)));
                FieldOF.set_ofTp_fName_instance_val_.accept(DashboardApiWare.class,"postHandlers",null,headers);
                return Boolean.TRUE;
            }catch (Throwable e){return Boolean.FALSE; /*Predicate LocalEnv is ready always.*/ }
        }
    }



    public static class WebDashboardApiRegister extends ICasuePluginsRegister{
        @Override
        public String environmentPropertiesPrefix() {
            return null;
        }

        @Override
        public Class environmentPropertiesType() {
            return null;
        }

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
            scanner.scan("icasue.webdashboard.api", "icasue.runninginvoke.api");
        }
    }
}
